﻿using System;
using System.Collections.Generic;
using System.IO;
using System.Xml;
using System.Xml.Linq;
using log4net;
using StatesConverter.Model;

///
/// TODO Extract duplicate code into other methods, make it look un-retarded (that is a word) and clean up code
/// 
/// 
namespace StatesConverter.Logic
{
	public class StatesConfigProcessor
	{
		private readonly ILog logger = LogManager.GetLogger(typeof(StatesConfigProcessor));
		private readonly string pathToSave;
		private readonly Stream statesConfig;
		private int roundNodeCount = 1000000;


		public StatesConfigProcessor(
			Stream statesXml,
			string outputFilepath
			)
		{
			statesConfig = statesXml;
			pathToSave = outputFilepath;
		}


		public string ProcessStatesConfig()
		{
			string returnMessage = "";

			try
			{
				StatesToXgmlConverter converter = new StatesToXgmlConverter();

				// Key is the State Id, value is the XElement
				Dictionary<int, XElement> states = new Dictionary<int, XElement>();
				List<int> takenIds = new List<int>();
				List<XElement> edges = new List<XElement>();

				Dictionary<string, Transition> transitionsToConvert = new Dictionary<string, Transition>();

				XmlReader reader = XmlReader.Create(this.statesConfig);

				XDocument document = XDocument.Load(reader);

				// Parse nodes into a single collection of state nodes
				IEnumerable<XElement> nodes = document.Descendants("state");
				IEnumerable<XElement> transitionGroups = document.Descendants("transitionGroup");

				XElement output = converter.CreateXgmlHeader();
				XElement graph = converter.CreateGraphHeader();

				// Populate the dictionary to later test for state id's that are
				// in transitions but not as any final state in the currently processed file.
				foreach (var node in nodes)
				{
					states.Add(int.Parse(node.Attribute("id").Value), node);
					takenIds.Add(int.Parse(node.Attribute("id").Value));
				}

				// Pass along all states to the converter and append the lists to the aggregate xml list
				foreach (XElement stateElement in states.Values)
				{
					if (stateElement.Attribute("type") != null)
					{
						if (stateElement.Attribute("type").Value == "action")
						{
							graph.Add(converter.CreateActionNode(stateElement));
						}
						else if (stateElement.Attribute("type").Value == "page")
						{
							graph.Add(converter.CreatePageNode(stateElement));
						}
						else if (stateElement.Attribute("type").Value == "jsonend")
						{
							graph.Add(converter.CreateOctagonNode(stateElement));
						}

						// Create edge elements for each transition
						IEnumerable<XElement> transitions = stateElement.Descendants("transition");
						IEnumerable<XElement> stateTransitionGroups = stateElement.Descendants("transitionGroup");

						foreach (var transition in transitions)
						{
							// Check if the target state is in the states.config, if not then create a new round node
							// Pull this procedure out and see if it can be made into a reusable function.
							string source = stateElement.Attribute("id").Value;
							string target = transition.Attribute("targetState").Value;
							string tmpKey = source + "|" + target;

							if (states.ContainsKey(int.Parse(target)))
							{
								if (!transitionsToConvert.ContainsKey(tmpKey))
								{
									Transition tmpTran = new Transition();
									tmpTran.Source = source;
									tmpTran.Target = target;
									tmpTran.Name = transition.Attribute("name").Value;
									transitionsToConvert.Add(tmpKey, tmpTran);
								}
								else
								{
									transitionsToConvert[tmpKey].Name += Environment.NewLine + transition.Attribute("name").Value;
								}
							}
							else
							{
								if (!transitionsToConvert.ContainsKey(tmpKey))
								{
									Transition tmpTran = new Transition();
									tmpTran.Source = source;
									tmpTran.Target = roundNodeCount.ToString();
									tmpTran.Name = transition.Attribute("name").Value;

									transitionsToConvert.Add(tmpKey, tmpTran);
									graph.Add(converter.CreateEllipseNode(roundNodeCount.ToString(), target));
									roundNodeCount++;
								}
								else
								{
									transitionsToConvert[tmpKey].Name += Environment.NewLine + transition.Attribute("name").Value;
								}
							}
						}

						foreach (XElement group in stateTransitionGroups)
						{
							string source = stateElement.Attribute("id").Value;
							string label = group.Attribute("name").Value;
							string tmpKey = source + "|" + roundNodeCount.ToString();

							if (!transitionsToConvert.ContainsKey(tmpKey))
							{
								Transition groupTran = new Transition();
								groupTran.Source = source;
								groupTran.Name = label;
								groupTran.Target = roundNodeCount.ToString();

								transitionsToConvert.Add(tmpKey, groupTran);
								graph.Add(converter.CreateDiamondNode(roundNodeCount.ToString(), label));
								roundNodeCount++;
							}
							else
							{
								transitionsToConvert[tmpKey].Name += Environment.NewLine + label;
							}

						}
					}
				}
				// Parse all transition groups here in the similar manner as states with their child transitions
				foreach (XElement transitionGroup in transitionGroups)
				{
					if (transitionGroup.HasElements)
					{
						string source = roundNodeCount.ToString();
						graph.Add(converter.CreateGroupNode(source, transitionGroup));
						roundNodeCount++;

						IEnumerable<XElement> transitions = transitionGroup.Descendants("transition");
						foreach (XElement transition in transitions)
						{
							string target = transition.Attribute("targetState").Value;
							string transKey = source + "|" + target;

							if (!transitionsToConvert.ContainsKey(transKey))
							{
								Transition transGroupTran = new Transition();
								transGroupTran.Source = source;
								transGroupTran.Target = roundNodeCount.ToString();
								transGroupTran.Name = transition.Attribute("name").Value;


								transitionsToConvert.Add(transKey, transGroupTran);
								graph.Add(converter.CreateEllipseNode(roundNodeCount.ToString(), target));
								roundNodeCount++;
							}
							else
							{
								transitionsToConvert[transKey].Name += Environment.NewLine + transition.Attribute("name");
							}
						}
					}
				}

				foreach (XElement edge in edges)
				{
					graph.Add(edge);
				}
				foreach (Transition edge in transitionsToConvert.Values)
				{
					graph.Add(converter.CreateEdgeNode(edge));
				}


				output.Add(graph);

				// Create an xml file to save
				XDocument outputDocument = new XDocument(
					new XDeclaration("1.0", "utf-8", ""),
					output
					);

				outputDocument.Save(pathToSave);
				statesConfig.Close();

				returnMessage = "File Processed Successfully.";
			}
			catch (Exception ex)
			{
				logger.Error("Error processing states file:", ex);
				statesConfig.Close();
				returnMessage = ex.Message;
			}

			return returnMessage;
		}
	}
}